package network

import (
	"context"
	"errors"
	"net"
	"strings"

	"gitee.com/simonxie979/skymeta/logutil"
)

// 网络通信对象
type Communicator[T any] struct {
	ctx    context.Context
	cancel context.CancelFunc

	listener   *net.TCPListener // TCP端口监听器
	sessionMng *sessionMng[T]   // 会话管理器
	host       ICommHost[T]     // 宿主对象
	log        *logutil.Logger  // 日志对象
}

// ListenTCP Listen TCP networks by type and address.
func (comm *Communicator[T]) ListenTCP(connType, address string) (err error) {
	addr, err := net.ResolveTCPAddr(connType, address)
	if err != nil {
		return err
	}

	comm.listener, err = net.ListenTCP(connType, addr)
	if err != nil {
		return err
	}

	go func() {
		defer func() {
			comm.listener.Close()
			comm.listener = nil
		}()
		for {
			select {
			case <-comm.ctx.Done():
				return
			default:
			}

			conn, err := comm.listener.AcceptTCP()
			if err != nil {
				if strings.Contains(err.Error(), "use of closed network connection") {
					return
				}

				comm.log.Errorf("communicator", "accept tcp connection failure.err: %v", err)
				continue
			}
			conn.SetNoDelay(true)

			session := newSession(comm.ctx, conn, comm)
			comm.sessionMng.Add(session)

			// Start listening the socket in advance
			session.Start()

			comm.host.OnConnect(session.guid, session.GetRemoteAddr())
		}
	}()

	return nil
}

// EstablishTCP Dial to TCP networks by type and address.
func (comm *Communicator[T]) EstablishTCP(connType, address string) (sessionID uint64, err error) {
	conn, err := net.Dial(connType, address)
	if err != nil {
		return 0, err
	}

	if tcpConn, ok := conn.(*net.TCPConn); ok {
		tcpConn.SetNoDelay(true)
	}

	session := newSession(comm.ctx, conn, comm)
	comm.sessionMng.Add(session)

	// Start listening the socket in advance
	session.Start()

	return session.guid, nil
}

// SendMsg Send message to session's connection by session id.
func (comm *Communicator[T]) SendMsg(guid uint64, msg T) (success bool) {
	session := comm.sessionMng.Get(guid)
	if session == nil {
		return false
	}

	return session.Send(msg)
}

// BordcastMsg Send message to all of already exist session's connection.
func (comm *Communicator[T]) BordcastMsg(msg T) {
	list := comm.sessionMng.GetAll()
	for _, session := range list {
		session.Send(msg)
	}
}

// Disconnect Activitily disconnect the session's connection by session id.
func (comm *Communicator[T]) Disconnect(guid uint64) {
	session := comm.sessionMng.Get(guid)
	if session == nil {
		return
	}

	// Will be calling function of OnDisconnect() in session.Close()
	// This was done to unify disconnection process
	session.Close(errors.New(CloseFrom_Self))
}

// Close Stop network communication, and close all of sessions.
func (comm *Communicator[T]) Close() {
	comm.cancel()

	err := errors.New(CloseFrom_Self)

	list := comm.sessionMng.RemoveAll()
	for _, session := range list {
		session.Close(err)
	}
}
